package dovs;

import dovs.node.*;

import java.util.*;

/** 
 * A number of convenient intertype methods for switching
 * on and extracting information from various AST nodes.
 *
 * <pre>
 * Summary of injected fields and methods:
 *  +   indicate fields copied on clone() 
 * -----------------------------------------------------------------------------
 * ABlock:
 *      Map<String,ALocalDecl> local_env;           // Environments
 *  
 * ABody: 
 *      int max_stack;                              // Limits 
 *      int max_locals;                             // Limits
 *      List<Instruction> instructions;             // CodeGeneration 
 * 
 * ABooleanConstExp:
 *      boolean value;                              // Weeding
 *      boolean isConstant();                       // ConstantFolding 
 * 
 * ACharConstExp: 
 *      int value;                                  // Weeding
 * 
 * AConstructorDecl:
 *      String signature;                           // Resources
 * 
 * AFieldDecl:
 *      String signature;                           // Resources
 * 
 * AIntConstExp:
 *      int value;                                  // Weeding
 *      boolean isConstant();                       // ConstantFolding 
 * 
 * ALocalDecl:  
 *      int index;                                  // Resources
 * 
 * ALocalLvalue:
 *      ALocalDecl local_decl;                      // Disambiguation
 * 
 * AMethodDecl:
 *      String signature;                           // Resources
 * 
 * ANamedType: 
 *  +   PTypeDecl decl;                             // TypeLinking
 * 
 * ANewExp: 
 *      AConstructorDecl constructor_decl;          // TypeChecking
 * 
 * ANonstaticFieldLvalue: 
 *      AFieldDecl field_decl;                      // TypeChecking
 * 
 * ANonstaticInvokeExp: 
 *      AMethodDecl method_decl;                    // TypeChecking
 * 
 * AProgram:
 *      Map<String,PTypeDecl> type_env;             // Environments
 * 
 * ASourceFile: 
 *      String file_name;                           // Util
 *      
 * AStaticFieldLvalue: 
 *      AFieldDecl field_decl;                      // TypeChecking
 * 
 * AStaticInvokeExp: 
 *      AMethodDecl method_decl;                    // TypeChecking
 * 
 * AStringConstExp:
 *      boolean isConstant();                       // ConstantFolding 
 *      String value;                               // Weeding
 * 
 * ASuperStm: 
 *      AConstructorDecl constructor_decl;          // TypeChecking
 * 
 * AThisStm: 
 *      AConstructorDecl constructor_decl;          // TypeChecking
 * 
 * PExp:    
 *      PType type;                                 // TypeChecking
 *      boolean isConstant();                       // ConstantFolding 
 * 
 * PLvalue: 
 *      PType type;                                 // TypeChecking
 * 
 * PTypeDecl:
 *      String canonical_name;                      // Environments 
 *      Map<String,AFieldDecl> field_env;           // Environments 
 *      Map<String,Set<AMethodDecl>> method_env;    // Environments 
 *  +   ANamedType type;                            // TypeLinking 
 *      String signature;                           // Resources
 * -------------------------------------------------------------------------
 *</pre>
 */
public aspect Util {

	// Some useful methods
	/** 
	 *	Construct a named type from the given fully qualified class or interface
	 *	name and optionally links it to its classpath declaration.
	 *  @param typename the name of the type.
	 *  @param link 
	 *  	if true, links the type to its declaration, in which case, the 
	 *  	class must be declared in the classpath.
	 */
	public static ANamedType makeType(String typename, boolean link) {
		List<TIdentifier> ids = new ArrayList<TIdentifier>();
		StringTokenizer tk = new StringTokenizer(typename, ".");
		while (tk.hasMoreTokens()) {
			ids.add(new TIdentifier(tk.nextToken()));
		}
		ANamedType type = new ANamedType(new AQualifiedName(ids));
		if (link) {
			type.decl = ClassEnvironment.lookupNamedType(typename);
			if (type.decl == null) {
				Errors.fatalErrorMessage(ErrorType.MAKE_TYPE_ERROR,
						"Could not find type " + typename);
			}
		}
		return type;
	}

	/** 
	 * 	Construct an integer constant representing the given integer value.
	 *	@param value the integer value for the constant.
	 *	@return the constant node.
	 */
	public static AIntConstExp makeIntConst(int value) {
		AIntConstExp c = new AIntConstExp(new TIntegerLiteral("" + value));
		c.value = value;
		return c;
	}

	/** 
	 * 	Construct a string constant representing the given string value.
	 *	@param value the string value for the constant.
	 *	@return the constant node.
	 */
	public static AStringConstExp makeStringConst(String value) {
		AStringConstExp c = new AStringConstExp(new TStringLiteral(value));
		c.value = value;
		return c;
	}

	/**
	 * 	Construct a boolean constant representing the given boolean value.
	 *  @param value the boolean value for the constant.
	 *  @return the constant node.
	 */
	public static ABooleanConstExp makeBooleanConst(boolean value) {
		ABooleanConstExp c = new ABooleanConstExp(value ? new ATrueBool() : new AFalseBool());
		c.value = value;
		return c;
	}

	/**
	 *	Returns a method signature string of the method declaration including
	 *	modifiers and return type. Intended for error messaging and does not
	 *	result in a consistent representation throughout all phases (because of
	 *	the use of {@link #ajc$PType$typeName()} defined below.
	 *	@param	node	the method declaration
	 *	@return a method signature string of the method declaration
	 */
	public static String getMethodSignatureString(AMethodDecl node) {
		StringBuffer sb = new StringBuffer();
		sb.append(node.getAccess().accessText());
		sb.append(" ");
		if (node.isStatic()) {
			sb.append("static ");
		}
		sb.append(node.getReturnType().typeName());
		sb.append(" ");
		sb.append(getMethodOrConstructorSignatureString(node));
		return sb.toString();
	}

	private static String getMethodOrConstructorSignatureString(
			MethodOrConstructor node) {
		StringBuffer sb = new StringBuffer();
		sb.append(node.getName().getText());
		sb.append("(");
		boolean first = true;
		for (ALocalDecl formal : node.getFormals()) {
			if (!first) {
				sb.append(",");
			}
			sb.append(formal.getType().typeName());
			first = false;
		}
		sb.append(")");
		return sb.toString();
	}

	/**
	 *	Returns {@code true} if the method declaration is final.
	 *	@return {@code true} if the method declaration is final
	 */
	public boolean AMethodDecl.isFinal() {
		return getFinal() != null;
	}

	/**
	 *	Returns {@code true} if the method declaration is static.
	 *	@return {@code true} if the method declaration is static
	 */
	public boolean AMethodDecl.isStatic() {
		return getStatic() != null;
	}

	/**
	 *	Returns {@code true} if the method declaration is abstract.
	 *	@return {@code true} if the method declaration is abstract
	 */
	public boolean AMethodDecl.isAbstract() {
		return getAbstract() != null;
	}

	/**
	 *	Returns {@code true} if the field declaration is final.
	 *	@return {@code true} if the field declaration is final
	 */
	public boolean AFieldDecl.isFinal() {
		return getFinal() != null;
	}

	/**
	 *	Returns {@code true} if the field declaration is static.
	 *	@return {@code true} if the field declaration is static
	 */
	public boolean AFieldDecl.isStatic() {
		return getStatic() != null;
	}

	/**
	 *	Returns {@code true} if the class declaration is final.
	 *	@return {@code true} if the class declaration is final
	 */
	public boolean AClassTypeDecl.isFinal() {
		return getFinal() != null;
	}

	/**
	 *	Returns {@code true} if the class declaration is abstract.
	 *	@return {@code true} if the class declaration is abstract
	 */
	public boolean AClassTypeDecl.isAbstract() {
		return getAbstract() != null;
	}

	/**
	 * 	Returns a token for the {@link PType}.
	 * 	@return	a token for the {@link PType}
	 */
	public Token PType.getToken() {
		throw new UnsupportedOperationException(getClass() + ".getToken()");
	}

	/**
	 * 	Returns a token for the {@link ANamedType}.
	 * 	@return a token for the {@link ANamedType}
	 */
	public Token ANamedType.getToken() {
		return getName().getToken();
	}

	/**
	 * 	Throws an {@link InternalCompilerError} if called, since
	 * 	{@link ANullType} has no token. {@link ANullType} is not
	 * 	created by the grammar and thus represents no concrete input.
	 *	@throws InternalCompilerError 
	 */
	public Token ANullType.getToken() {
		throw new InternalCompilerError("ANullType has no token");
	}

	/**
	 *	Returns the printed name of the type. This method cannot be used 
	 *	consistently until the TypeLinking phase has completed.
	 *	@return the printed name of the type.
	 */
	public String PType.typeName() {
		throw new UnsupportedOperationException(getClass() + ".typeName()");
	}


	public String AVoidType.typeName() {
		return "void";
	}

	public String AByteType.typeName() {
		return "byte";
	}

	public String AShortType.typeName() {
		return "short";
	}

	public String AIntType.typeName() {
		return "int";
	}

	public String ALongType.typeName() {
		return "long";
	}

	public String ACharType.typeName() {
		return "char";
	}

	public String AFloatType.typeName() {
		return "float";
	}

	public String ADoubleType.typeName() {
		return "double";
	}

	public String ABooleanType.typeName() {
		return "boolean";
	}

	public String AArrayType.typeName() {
		return getType().typeName() + "[]";
	}

	public String ANamedType.typeName() {
		return decl == null ? getName().nameText() : decl.canonical_name;
	}

	public String ANullType.typeName() {
		return "null";
	}

	/**
	 *	Returns {@code true} if this type a valid Joos type.
	 *	@return {@code true} if this type a valid Joos type
	 */
	public boolean PType.isJoosType() {
		return true;
	}

	public boolean ALongType.isJoosType() {
		return false;
	}

	public boolean AFloatType.isJoosType() {
		return false;
	}

	public boolean ADoubleType.isJoosType() {
		return false;
	}

	/**
	 *	Returns {@code true} if this type is a reference type.
	 * 	@return {@code true} if this type is a reference type
	 */
	public boolean PType.isReference() {
		return false;
	}

	public boolean ANamedType.isReference() {
		return true;
	}

	public boolean AArrayType.isReference() {
		return true;
	}

	public boolean ANullType.isReference() {
		return true;
	}

	/** 
	 * 	Returns {@code true} if this type is {@code java.lang.String}.
	 *  This method cannot be used safely until the TypeLinking
	 *  phase has completed.
	 *  @return {@code true} if this type is {@code java.lang.String}
	 */
	public boolean PType.isString() {
		return false;
	}

	public boolean ANamedType.isString() {
		return decl.canonical_name.equals("java.lang.String");
	}

	/**
	 * 	Returns {@code true} if this type is a numeric type, i.e. one
	 *	that can be coerced to int.
	 *	@return {@code true} if this type is a numeric type
	 */
	public boolean PType.isNumeric() {
		return false;
	}

	public boolean AByteType.isNumeric() {
		return true;
	}

	public boolean AShortType.isNumeric() {
		return true;
	}

	public boolean AIntType.isNumeric() {
		return true;
	}

	public boolean ACharType.isNumeric() {
		return true;
	}

	/**
	 *	Returns {@code true} if {@code other} is a {@link PType} which 
	 *	represents the same type as this type, i.e. if the two types are 
	 *	structurally equivalent.
	 *	@param	the object tested for equality
	 *	@return {@code true} if {@code other} represents the same type.
	 */
	public boolean PType.equals(Object other) {
		return other != null && kindPType() == ((PType) other).kindPType();
	}

	public boolean AArrayType.equals(Object other) {
		return super.equals(other)
			&& getType().equals(((AArrayType) other).getType());
	}

	public boolean ANamedType.equals(Object other) {
		return super.equals(other) && decl == ((ANamedType) other).decl;
	}

	/**
	 *	Returns a hashcode consistent with injected {@code equals} method.
	 *	@return a hashcode for this type.
	 */
	public int PType.hashCode() {
		return kindPType().hashCode();
	}

	public int AArrayType.hashCode() {
		return getType().hashCode();
	}

	public int ANamedType.hashCode() {
		return decl != null ? decl.hashCode() : kindPType().hashCode();
	}

	/** 
	 *	Returns the token which constitutes this binary operator in the source.
	 *  @return  the token which constitutes this binary operator in the source
	 */
	public Token PBinop.getToken() {
		throw new UnsupportedOperationException(getClass() + ".getToken()");
	}

	/**
	 *	Returns the textual representation of the operator.
	 *	@return the textual representation of the operator
	 */
	public String PBinop.opName() {
		return getToken().getText();
	}

	/**
	 *	Returns the token which constitutes this unary operator in the source.
	 *  @return the token which constitutes this unary operator in the source
	 */
	public Token PUnop.getToken() {
		throw new InternalCompilerError("Unop without token: "
				+ getClass().getName());
	}

	/**
	 *	Returns the textual representation of the operator.
	 *	@return the textual representation of the operator
	 */
	public String PUnop.opName() {
		return getToken().getText();
	}

	/**
	 *	Returns the token which constitutes this
	 *	increment/decrement operator in the source.
	 *	@return the token which constitutes this
	 *	increment/decrement operator in the source.
	 */
	public Token PIncDecOp.getToken() {
		throw new UnsupportedOperationException(getClass() + ".getToken()");
	}

	/**
	 *	Returns the textual representation of the operator.
	 *	@return	the textual representation of the operator
	 */
	public String PIncDecOp.opName() {
		return getToken().getText();
	}

	/**
	 *	Returns the textual representation of the name.
	 * 	@return the textual representation of the name
	 */
	public String PName.nameText() {
		throw new UnsupportedOperationException(getClass() + ".nameText()");
	}

	public String ASimpleName.nameText() {
		return getIdentifier().getText();
	}

	public String AQualifiedName.nameText() {
		StringBuilder sb = new StringBuilder();
		boolean first = true;
		for (TIdentifier id : getIdentifiers()) {
			if (!first) {
				sb.append(".");
			}
			sb.append(id.getText());
			first = false;
		}
		return sb.toString();
	}

	/**
	 *	Returns the token which constitutes this name in the source.
	 *	@return the token which constitutes this name in the source
	 */
	public TIdentifier PName.getToken() {
		throw new UnsupportedOperationException(getClass() + ".getToken()");
	}

	public TIdentifier ASimpleName.getToken() {
		return getIdentifier();
	}

	public TIdentifier AQualifiedName.getToken() {
		return getIdentifiers().get(0);
	}

	/**
	 * 	Returns the identifier(s) of this name as a list.
	 * 	@return the identifier(s) of this name as a list
	 */
	public List<TIdentifier> PName.getIdentifiers() {
		throw new UnsupportedOperationException(getClass() + ".getIdentifiers()");
	}

	public List<TIdentifier> ASimpleName.getIdentifiers() {
		return Collections.singletonList(getIdentifier());
	}

	/**
	 *	Returns the textual representation of the access privilege.
	 *	@return the textual representation of the access privilege
	 */
	public String PAccess.accessText() {
		throw new UnsupportedOperationException(getClass() + ".accessText()");
	}

	public String APublicAccess.accessText() {
		return "public";
	}

	public String AProtectedAccess.accessText() {
		return "protected";
	}

	/**
	 * 	Returns the value of a boolean constant.
	 * 	@return the value of a boolean constant
	 */
	public boolean PBool.getValue() {
		throw new UnsupportedOperationException(getClass() + ".getValue()");
	}

	public boolean ATrueBool.getValue() {
		return true;
	}

	public boolean AFalseBool.getValue() {
		return false;
	}

	/**
	 * {@code MethodOrConstructor} defines a common interface on
	 * {@link AMethodDecl} and {@link AConstructorDecl}.
	 */
	public interface MethodOrConstructor {
		/**
		 * Returns the access modifier of the method/constructor.
		 * 
		 * @return the access modifier of the method/constructor
		 */
		public PAccess getAccess();

		/**
		 * Returns the name of the method/constructor.
		 * 
		 * @return the name of the method/constructor
		 */
		public TIdentifier getName();

		/**
		 * Returns the formals of the method/constructor.
		 * 
		 * @return the formals of the method/constructor
		 */
		public LinkedList<ALocalDecl> getFormals();

		/**
		 * Returns the named types in the throws clause of the
		 * method/constructor.
		 * 
		 * @return the named types in the throws clause of the
		 *         method/constructor
		 */
		public LinkedList<ANamedType> getThrows();

		/**
		 * Returns the body of the method/constructor.
		 * 
		 * @return the body of the method/constructor
		 */
		public ABody getBody();
	}

	declare parents: AMethodDecl implements MethodOrConstructor;

	declare parents: AConstructorDecl implements MethodOrConstructor;

	/**
	 * {@code Invocation} defines a common interface for invocation, i.e.
	 * {@link ANewExp}, {@link AStaticInvokeExp}, {@link ANonstaticInvokeExp},
	 * {@link ASimpleInvokeExp}, {@link AAmbiguousInvokeExp},
	 * {@link ASuperStm} and {@link AThisStm}.
	 */
	public interface Invocation {
		/**
		 * Returns the name of the target of the invocation, i.e. the name of
		 * the invoked method or constructor.
		 * 
		 * @return the name of the target of the invocation
		 */
		public String getInvocationName();

		/**
		 * Returns the arguments of the invocation.
		 * 
		 * @return the arguments of the invocation
		 */
		public LinkedList<PExp> getArgs();

		/**
		 * Returns a token for the invocation to use for position information in
		 * error messages.
		 * 
		 * @return a token for the invocation
		 */
		public Token getToken();
	}

	public String ANewExp.getInvocationName() {
		return getType().typeName();
	}

	public String ANonstaticInvokeExp.getInvocationName() {
		return getName().getText();
	}

	public String AStaticInvokeExp.getInvocationName() {
		return getName().getText();
	}

	public String ASimpleInvokeExp.getInvocationName() {
		return getName().getText();
	}

	public String AAmbiguousInvokeExp.getInvocationName() {
		return getName().getText();
	}

	public String ASuperStm.getInvocationName() {
		return "super";
	}

	public String AThisStm.getInvocationName() {
		return "this";
	}

	public Token ANonstaticInvokeExp.getToken() {
		return getName();
	}

	public Token AStaticInvokeExp.getToken() {
		return getName();
	}

	public Token ASimpleInvokeExp.getToken() {
		return getName();
	}

	public Token AAmbiguousInvokeExp.getToken() {
		return getName();
	}

	declare parents: ANewExp implements Invocation;

	declare parents: AStaticInvokeExp implements Invocation;

	declare parents: ANonstaticInvokeExp implements Invocation;

	declare parents: ASimpleInvokeExp implements Invocation;

	declare parents: AAmbiguousInvokeExp implements Invocation;

	declare parents: ASuperStm implements Invocation;

	declare parents: AThisStm implements Invocation;

	/**
	 * Returns the signature of the invocation, i.e. the name of target and the
	 * types of the arguments. If called before TypeChecking argument types are
	 * not printed.
	 * 
	 * @param invocation
	 *            the invocation
	 * @return the signature of the invocation
	 */
	public static String getInvocationSignature(Invocation invocation) {
		StringBuffer sb = new StringBuffer();
		sb.append(invocation.getInvocationName());
		sb.append("(");
		boolean first = true;
		for (PExp arg : invocation.getArgs()) {
			if (!first) {
				sb.append(",");
			}
			if (arg.type != null) {
				sb.append(arg.type.typeName());
			}
			first = false;
		}
		sb.append(")");
		return sb.toString();
	}

	/**
	 * {@code FieldAccess} defines a common interface for field accesse, i.e.
	 * {@link ANonstaticFieldLvalue} and {@link AStaticFieldLvalue}.
	 */
	public interface FieldAccess {
		/**
		 * Returns the name of the accessed field.
		 * 
		 * @return the name of the accessed field
		 */
		public TIdentifier getName();
	}

	declare parents: ANonstaticFieldLvalue implements FieldAccess;
	declare parents: AStaticFieldLvalue implements FieldAccess;

	/** Field to associate ASourceFile with the filename of its source file * */
	private String ASourceFile.file_name;

	/**
	 * Sets the name of the source file.
	 * 
	 * @param file_name
	 *            the name of the source file
	 */
	public void ASourceFile.setFileName(String filename) {
		// ensure platform specific separators
		this.file_name = new java.io.File(filename).getPath();
	}

	/**
	 * Returns the name of the source file.
	 * 
	 * @return the name of the source file
	 */
	public String ASourceFile.getFileName() {
		return file_name;
	}

	// automatically copy some injected fields on .clone()
	after(PTypeDecl i) returning(Object o): target(i) && call(Object clone()) {
		// copy the decl field injected in TypeLinking
		PTypeDecl a = (PTypeDecl) o;
		a.type = i.type;
	}

	after(ANamedType i) returning(Object o): target(i) && call(Object clone()) {
		// copy the type field injected in TypeLinking
		ANamedType a = (ANamedType) o;
		a.decl = i.decl;
	}
}
